/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.security.model;

import java.util.Collection;
import java.util.Set;
import java.util.UUID;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.solr.analysis.LowerCaseFilterFactory;
import org.apache.solr.analysis.NGramFilterFactory;
import org.apache.solr.analysis.WhitespaceTokenizerFactory;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Parameter;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenFilterDef;
import org.hibernate.search.annotations.TokenizerDef;
import org.libermundi.theorcs.core.model.Account;
import org.libermundi.theorcs.core.model.Gender;
import org.libermundi.theorcs.core.model.Searchable;
import org.libermundi.theorcs.core.model.base.Labelable;
import org.libermundi.theorcs.security.SecurityConstants;
import org.libermundi.theorcs.security.dao.hibernate.listener.PasswordListener;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.google.common.collect.Sets;


/**
 * User Model Object. 
 * Serializable for Hibernate
 */
@Entity(name="User")
@Table(name=SecurityConstants.TBL_USER)
@Indexed(index="User")
@EntityListeners({PasswordListener.class})
@XmlRootElement(name="user")
@AnalyzerDef(name = "SuggestionAnalyzer",
	//Split input into tokens according to tokenizer
	tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class), //
	filters = { //
	//Normalize token text to lowercase, as the user is unlikely to care about casing when searching for matches
	@TokenFilterDef(factory = LowerCaseFilterFactory.class),
	//Index partial words starting at the front, so we can provide Autocomplete functionality
	@TokenFilterDef(factory = NGramFilterFactory.class, params = { @Parameter(name = "maxGramSize", value = "1024") }),
	//Close filters & Analyzerdef
})
@Analyzer(definition = "SuggestionAnalyzer")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class User extends UidUserStatefulEntity implements UserDetails, Account, Searchable, Labelable {
	public final static String PROP_AVATAR="avatar";
	public final static String PROP_DESCRIPTION="description";
	public final static String PROP_THUMBNAIL="thumbnail";
	public final static String PROP_CELLULARNUMBER="cellularNumber";
	public final static String PROP_PHONENUMBER="phoneNumber";
	public final static String PROP_ADDRESS="address";
	public final static String PROP_EMAIL="email";
	public final static String PROP_FIRSTNAME="firstName";
	public final static String PROP_LASTNAME="lastName";
	public final static String PROP_GENDER="gender";
	public final static String PROP_SALUTATION="salutation";
	public final static String PROP_NICKNAME="nickName";
	public final static String PROP_USERNAME="username";
	public final static String PROP_PASSWORD = "password";
	public final static String PROP_ENABLED = "enabled";
	public final static String PROP_ACCOUNT_NON_EXPIRED = "accountNonExpired";
	public final static String PROP_CREDENTIALS_NON_EXPIRED = "credentialsNonExpired";
	public final static String PROP_ACCOUNT_NON_LOCKED = "accountNonLocked";
	public final static String PROP_ACTIVATION_KEY = "activationKey";
	
	private static final long serialVersionUID = 984592116709403247L;
	private static final String[] SEARCHABLE_FIELDS = new String[]{PROP_NICKNAME, PROP_DESCRIPTION};

	private Gender _gender=Gender.BOTH;
	private Salutation _salutation=Salutation.NONE;
	private String _firstName;
	private String _lastName;
	private String _nickName;
	private String _username;
	private String _password;
	private boolean _enabled=false;
	private String _email;
	private String _avatar;
	private String _description;
	private String _thumbnail;
	private String _phoneNumber;
	private String _cellularNumber;
	private String _activationKey;
	private Set<Authority> _authorities = Sets.newHashSet();
	private Address _address;
	private boolean _accountNonExpired;
	private boolean _credentialsNonExpired;
	private boolean _accountNonLocked;

	@Override
	@Basic
	@Column(name=User.PROP_USERNAME,length=30,unique=true,nullable=false)
	public String getUsername() {
		return _username;
	}

	@Override
	@Basic
	@Column(name=User.PROP_PASSWORD,length=128,nullable=false)
	@XmlTransient
	public String getPassword() {
		return _password;
	}

	@Basic
	@Column(name=User.PROP_AVATAR,length=255,nullable=true)
	public String getAvatar() {
		return _avatar;
	}

	@Basic
	@Column(name=User.PROP_THUMBNAIL,length=255,nullable=true)
	public String getThumbnail() {
		return _thumbnail;
	}

	@Basic
	@Column(name=User.PROP_CELLULARNUMBER,length=25,nullable=true)
	public String getCellularNumber() {
		return _cellularNumber;
	}

	@Basic
	@Column(name=User.PROP_PHONENUMBER,length=25,nullable=true)
	public String getPhoneNumber() {
		return _phoneNumber;
	}

	@Embedded
	public Address getAddress() {
		return _address;
	}

	@Override
	@Basic
	@Column(name=User.PROP_EMAIL,length=255,nullable=false)
	public String getEmail() {
		return _email;
	}

	@Basic
	@Column(name=User.PROP_FIRSTNAME,length=30,nullable=true)
	public String getFirstName() {
		return _firstName;
	}

	@Basic
	@Column(name=User.PROP_LASTNAME,length=50,nullable=true)
	public String getLastName() {
		return _lastName;
	}

	@Enumerated(EnumType.STRING)
	@Column(name=User.PROP_GENDER,length=7,nullable=false)
	public Gender getGender() {
		return _gender;
	}
	
	@Enumerated(EnumType.STRING)
	@Column(name=User.PROP_SALUTATION,length=4,nullable=false)
	public Salutation getSalutation() {
		return _salutation;
	}
	
	@Basic
	@Column(name=User.PROP_NICKNAME,length=20,nullable=true,unique=true)
	@Field(store=Store.YES,index=Index.YES)
	public String getNickName() {
		return _nickName;
	}

	@Lob
	@Column(name=User.PROP_DESCRIPTION,nullable=true)
	@Field(store=Store.YES,index=Index.YES)
	public String getDescription() {
		return _description;
	}	
	
	@Override
	@Basic
	@Column(name=User.PROP_ENABLED)
	public boolean isEnabled() {
		return _enabled;
	}

    @ManyToMany(
    		cascade={CascadeType.PERSIST,CascadeType.MERGE},
    		fetch=FetchType.LAZY
    )    
    @JoinTable(name=SecurityConstants.TBL_USER2AUTHORITIES,joinColumns={@JoinColumn(name="userId")},inverseJoinColumns={@JoinColumn(name="authorityId")})
    @LazyCollection(LazyCollectionOption.EXTRA)
	@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
	@XmlTransient
	public Set<Authority> getRoles() {
		return _authorities;
	}
	
	
	/* (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetails#getAuthorities()
	 */
	@Override
	@Transient
	@XmlTransient
	public Collection<GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> auth = Sets.newHashSet();
		for (GrantedAuthority grantedAuthority : _authorities) {
			auth.add(new SimpleGrantedAuthority(grantedAuthority.getAuthority())); // This way we make sure we compare always SimpleGrantedAuthority in equals methods.
		}
		return auth;
	}

	/* (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetails#isAccountNonExpired()
	 */
	@Override
	@Basic
	@Column(name=User.PROP_ACCOUNT_NON_EXPIRED)
	public boolean isAccountNonExpired() {
		return _accountNonExpired;
	}
	
	/* (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetails#isAccountNonLocked()
	 */
	@Override
	@Basic
	@Column(name=User.PROP_ACCOUNT_NON_LOCKED)
	public boolean isAccountNonLocked() {
		return _accountNonLocked;
	}
	
	/* (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetails#isCredentialsNonExpired()
	 */
	@Override
	@Basic
	@Column(name=User.PROP_CREDENTIALS_NON_EXPIRED)
	public boolean isCredentialsNonExpired() {
		return _credentialsNonExpired;
	}

	@Basic
    @Column(name = User.PROP_ACTIVATION_KEY,length=36)
	public String getActivationKey() {
		return _activationKey;
	}
	
	@Transient
	public String getFullName() {
		return getFirstName() + " " + getLastName();
	}
	
	public void setAvatar(String avatar) {
		this._avatar = avatar;
	}	
	
	public void setThumbnail(String thumbnail) {
		this._thumbnail = thumbnail;
	}	
	
	public void setCellularNumber(String cellularNumber) {
		this._cellularNumber = cellularNumber;
	}

	public void setPhoneNumber(String phoneNumber) {
		this._phoneNumber = phoneNumber;
	}
	
	public void setAddress(Address address) {
		this._address = address;
	}
	
	public void setEmail(String email) {
		this._email = email.toLowerCase();
	}

	public void setFirstName(String firstName) {
		this._firstName = firstName;
	}
	
	public void setLastName(String lastName) {
		this._lastName = lastName;
	}

	public void setGender(Gender gender) {
		this._gender = gender;
	}
	
	public void setSalutation(Salutation salutation) {
		this._salutation = salutation;
	}
	
	public void setNickName(String nickName) {
		this._nickName = nickName;
	}
	
	public void setDescription(String description) {
		this._description = description;
	}
	
	public void setPassword(String password) {
		this._password = password;
	}

	public void setEnabled(boolean enabled) {
		this._enabled = enabled;
	}

	public void setUsername(String username) {
		_username = username;
	}

	public void setRoles(Set<Authority> authorities) {
		_authorities = authorities;
	}
	
	public void setActivationKey(String key){
		this._activationKey = key;
	}

	public void setAccountNonExpired(boolean accountNonExpired) {
		_accountNonExpired = accountNonExpired;
	}

	public void setCredentialsNonExpired(boolean credentialsNonExpired) {
		_credentialsNonExpired = credentialsNonExpired;
	}

	public void setAccountNonLocked(boolean accountNonLocked) {
		_accountNonLocked = accountNonLocked;
	}
	
	public String resetActivationKey(){
		_activationKey = UUID.randomUUID().toString();
		return _activationKey;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "[User [Id="+ getId()+ "][Uid="+ getUid()+ "][FullName="+ getFullName()+ "][Email="+ getEmail()+ "]]";
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Searchable#loadSearchfields()
	 */
	@Override
	public String[] loadSearchfields() {
		return SEARCHABLE_FIELDS;
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.base.Labelable#printLabel()
	 */
	@Override
	public String printLabel() {
		return getFullName();
	}
}
